home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 52
/
Aminet 52 (2002)(GTI - Schatztruhe)[!][Dec 2002].iso
/
Aminet
/
docs
/
mags
/
saku09.lha
/
Teksti
/
C-kurssi.txt
< prev
next >
Wrap
Text File
|
1994-11-01
|
47KB
|
1,230 lines
5
1*
{3 C-ohjelmointikurssi - Osa 1
{3 ---------------------------
Ville-Pertti Keinonen
Ensimmäinen osa:
- johdantoa
- lähdekoodi
- kääntäjä
- muotoilu
- funktiot, tyypit ja muuttujat alustavasti
- lauseet, lausekkeet (perus-operaatiot)
- vertailuoperaattorit, if-rakenne
{3C-kieli ja muut kielet, eli miksi kannattaa opetella C:tä
Alunperin C-kieli on kehitetty UNIX-käyttöjärjestelmän toteutusta varten
PDP-tietokoneilla. Kieli onnistui kuitenkin siinä määrin hyvin, että se on
yleistynyt lähes kaikentyyppisillä tietokonelaitteistoilla. Amigalla C-kieli
on ohjelmointikielistä ehkä merkittävin, koska Amigan käyttöjärjestelmä on
suurelta osin sillä kirjoitettu.
C-kieli on rakenteellinen ohjelmointikieli. Sen hallinta vaatii enemmän har-
joitusta kuin matalamman tason (esim. assembler) tai korkeamman tason (Ami-
galla pääasiassa BASICit ja ARexx) kielet, mutta siitä huolimatta sen opet-
teleminen on vaivan arvoista, sillä C-kielellä on huomattavia etuja molem-
piin luokituksiin verrattuna:
Matalamman tason kieliin verrattuna C-kieli on vähemmän vaivalloista ja bu-
gialtista. Ohjelmointi on siis huomattavasti nopeampaa ja mukavampaa. Samoin
mahdollisten bugien määrä on pienempi ja ilmenevien bugien etsintä nopeam-
paa, kuin assemblerilla ohjelmoitaessa. Monilla assembleria käyttävillä oh-
jelmoijilla, jotka eivät osaa C:tä, on vahvasti se käsitys, että C olisi BA-
SICin kaltainen korkean tason kieli ja tuottaisi hidasta koodia. Totta on,
että C-kääntäjän tuottama koodi ei ole aivan niin tehokasta kuin vastaava
koodi huolellisesti assemblerilla tehtynä (mihin kuluu ohjelmoijalta aikaa
vähintään kymmenkertaisesti enemmän), mutta ero ei ole normaalitilanteissa
kovin suuri; se lienee noin 20-30% yksinkertaisilla kääntäjillä ja vähemmän
sellaisilla, jotka optimoivat tehokkaasti tuottamaansa koodia. Kuitenkin
joissain poikkeustilanteissa, joissa assemblerissa voisi tehdä jotain eri-
koisempia teknisiä optimointeja, voi ero olla niin suuri, että moni ohjel-
moija tekee ainakin kyseisen kohdan mieluummin assemblerilla. Mitä BASICin-
kaltaisuuteen tulee, osoittaa sen tyyppinen käsitys lähinnä
ymmärtämättömyyttä, sillä:
Korkeamman tason kieliin verrattuna C tuottaa käännettynä paljon tehokkaam-
paa koodia, koska sen rakenteet vastaavat hyvin läheisesti sitä, mitä kesku-
syksikkö käytännössä tekee. Korkeamman tason kielet ovat usein komentopoh-
jaisia, eivätkä niiden muuttujien tyypit vastaa keskusyksikön käsittelemiä,
joten jokaista komentoa tai lausekkeen osaa vastaa jokin kokonainen assemb-
ler-rutiini, joka on useimmiten aivan turhan monimutkainen kyseisen operaa-
tion suorittamiseen.
Amigalle on ilmestynyt uusi, ilmeisesti C:n haastajaksi tarkoitettu mel-
kein-rakenteellinen ohjelmointikieli, E, joka on helpompaa kuin C ja jossa
on nopeampi kääntäjä (joka tosin ei pahemmin optimoi tuotettua koodia) ja
mahdollisuus käyttää helpommin suoraan assemblerkomentoja koodin joukossa.
Omasta mielestäni E ei kuitenkaan sovellu oikein mihinkään, paitsi ehkä sel-
laisiin aivan pieniin ohjelmiin, jotka ovat assembleria, lukuunottamatta jo-
tain kutsuja E:n tai käyttöjärjestelmän korkeamman tason rutiineihin.
{3C-kielen kehitys
C-kieli on ajan mittaan muuttunut hieman alkuperäisestä muodostaan. Jotkut
asiat ovat olleet epäselviä, ja niiden toiminta on vaihdellut kääntäjästä
toiseen. Tämän vuoksi ANSI (American National Standard for Information Sys-
tems) loi X3J11:n, teknillisen toimikunnan, jonka tehtävänä oli standardi-
soida C-kieli. Tämä standardisoitu muoto C-kielestä tunnetaan nykyisin ylei-
sesti "ANSI C":nä. Muista C:n "määritelmistä" lienee merkittävin K&R (viit-
taa Brian Kernighanin ja Dennis Ritchien teokseen C-kielestä), johon ANSI C
osin pohjautuu.
Suurin osa Amigan C-kääntäjistä on pääpiirteiltään ANSI C-standardin mukai-
sia. Useimmiten kuitenkin joitain asioita on laajennettu, ja kääntäjät ovat
joidenkin asioiden suhteen suvaitsevampia kuin standardin mukaisen kääntäjän
tulisi olla. Tässä kurssissa pyrin parhaiden tietojeni mukaan mainitsemaan
kyseisenlaiset poikkeamat niihin liittyvän materiaalin yhteydessä.
{3Tämä kurssi
Tämän kurssin tarkoituksena on selittää C-kielen perusteet ja antaa hieman
pitemmällekin menevää tietoa sillä ohjelmoinnista, eli kurssista lienee apua
myös monille sellaisille, jotka jo osaavat C:tä.
Pelkkä kurssin lukeminen ei kuitenkaan riitä C-kielen ohjelmointiin: täytyy
myös pyrkiä ymmärtämään lukemansa, joten suosittelen, että luette kaiken
huolellisesti ja etenette seuraavaan kohtaan vasta ymmärrettyänne edellisen
kohdan mahdollisimman hyvin. Muistakaa myös, että kertaus ja käytännön ko-
keilut auttavat muistamaan asioita. Tämä kurssi ei takaa ohjelmointitaidon
kehittymistä, mutta jos kykenee ymmärtämään sisällön ja näiden tietojen pe-
rusteella lähtee rohkeasti kokeilemaan ja harjoittelemaan ohjelmointia,
pitäisi vähitellen syntyä C-kieleen hyvä tuntuma, jonka avulla voikin jo
aloittaa varsinaisen ohjelmoinnin.
Tämä kurssi ei sinänsä vaadi mitään aiempaa ohjelmointitaitoa, mutta assemb-
lerin jonkinasteinen hallitseminen on hyödyksi, sillä se helpottaa koneen
toiminnan ja joidenkin käsitteiden käytännön merkityksen ymmärtämistä. Jon-
kinlainen C-kääntäjä on hyvä olla, jotta voi myös kokeilla oppimiaan asioi-
ta. Amigalle on saatavilla ainakin seuraavia kääntäjiä:
{3Aztec (Manx) C
Tätä ei varmaan enää moni käytä. Aztec on vanhanaikainen (ja vanha)
kääntäjä, aikoinaan se on ollut ilmeisesti aika suosittukin. En tiedä, onko
tätä kääntäjää enää mistään saatavilla, enkä näe mitään syytä, miksi joku
haluaisi vielä nykyään käyttää Aztecia.
{3DICE C
DICE on alunperin Matthew Dillonin sharewarena julkaisema C-kääntäjä. Uudet
versiot ovat kaupallisia, mutta vapaasti levitettäviä versioita on vielä
liikkeellä, ja ne ovat ominaisuuksiltaan jo erittäin käyttökelpoisia. Vaikka
koneessa ei olisi paljon muistia, DICE toimii hyvin ja kääntää ohjelmat
erittäin nopeasti. DICE:n tuottama koodi on suhteellisen pientä ja nopeaa,
vaikka DICE ei suorita sille mitään monimutkaisempia optimointeja. Itse
käytän enimmäkseen DICE:n rekisteröityä shareware-versiota ohjelmien
kääntämiseen.
{3GNU C (eli GCC)
GCC on muista järjestelmistä portattu C-kääntäjä, joka on ilmainen (GNU li-
cense takaa ohjelman olevan täysin ilmainen, vapaasti käytettävissä, levi-
tettävissä ja jopa muuteltavissa). GCC kääntää ohjelmia kohtuullisen nopeas-
ti ja optimoi hyvin tehokkaasti, mutta vaatii suhteellisen paljon muistia.
GCC:n tuottamat ohjelmat vaativat ixemul.libraryn. GCC soveltuu parhaiten
käytettäväksi UNIX-tyyppisiä, helposti eri järjestelmille suoraan
käännettäviä ohjelmia tehtäessä.
{3SAS/C (entinen Lattice C)
SAS/C on ollut jo pitkään Amigan suosituin C-kääntäjä. SAS/C on kaupallinen,
mutta sen kehitys on lopetettu. SAS/C toimii suhteellisen vähällä muistilla
ja kääntää kohtuullisen nopeasti, jos optimointi on pois päältä, mutta tuot-
taa erittäin hidasta koodia. Optimoinnin kanssa tuotettu koodi on paljon te-
hokkaampaa, mutta kääntäminen on erittäin hidasta ja muistia kuluu monissa
tapauksissa valtavia määriä.
Myös joitain pienempiä, ilmaisia kääntäjiä on saatavilla (ainakin PDC ja
North C), mutta ne ovat vanhoja ja hyvin alkeellisia.
Jossain vaiheessa pitäisi ilmestyä Amigalle vielä yksi merkittävä ilmainen
C-kääntäjä, joka lienee harkitsemisen arvoinen. Olen nimittäin itse te-
kemässä kääntäjää. Toistaiseksi sillä ei ole vielä nimeä, mutta kääntäjän
pääosa on jo varsin pitkällä ja toimii hyvin niin pitkälle kuin sitä on ole-
massa. Generoidun koodin pitäisi olla GCC:n tuottaman tasoa (myös joitain
GCC:n ja muiden kääntäjien C-laajennuksia, kuten case rangeja ja __ty-
peof__:ia, tuetaan), mutta lisäksi toiminta soveltuu hyvin Amiga-
ympäristöön. Mainostan vain etukäteen, ettei kannata välttämättä ainakaan
ostaa C-kääntäjää, kurssin ajaksi ja muutenkin aloitteluvaiheeseen on DICE:n
ilmaisversio useimmille ehkä paras vaihtoehto. Täydellisempi kääntäjä olisi
tietysti GCC, joka on hyvä valinta, jos pitää erityisesti UNIX-tyyppisistä
ohjelmistoista ja omistaa riittävästi kovalevy- sekä muistitilaa.
Kääntäjän lisäksi on tietysti oltava jokin tekstieditori, jolla syöttää oh-
jelmat koneelle. Tällaisia on varmasti jokaisen saatavilla; Amigan Work-
bench-diskeillä tulee näitä jo kolme: ed, edit, memacs. Ne riittävät jo hy-
vin C-ohjelmoinnin opetteluun, mutta pitemmälle päästäessä on hyvä hankkia
jokin monipuolisempi (configuroitavuudeltaan) editori.
Kurssi pyrkii olemaan mahdollisimman kattava, mutta kuitenkin selkeä. Alussa
joudutaan käyttämään joidenkin asioiden esittämisessä esimerkkejä, joiden
varsinaisen sisällön merkitys selviää vasta myöhemmässä vaiheessa, erityi-
sesti koska kurssi pyrkii olemaan "oikeaoppinen" alusta alkaen.
Kurssissa käytetyt termit saattavat poiketa virallisista suomenkielisistä
termeistä, koska itse tunnen oikeat termit vain englanniksi. Monet asiat on
selitetty hieman kierrellen jonkun hankalan tai oudon kuuloisen termin
käytön välttämiseksi. Joissain yhteyksissä on myös englanninkielinen termi
mainittu.
{3Lähdekoodi ja käännös
C-kieltä ohjelmoitaessa kirjoitetaan ensin ohjelma tekstieditorilla ja tal-
lennetaan se tiedostoon. Tätä tiedostoa sanotaan lähdekoodiksi. Yleisemmin
käytetään kuitenkin vastaavasta englanninkielisestä termistä "source code"
johdettua slanginimitystä "sorsa". C-kielen lähdekooditiedostot nimetään
yleensä käyttäen päätettä ".c".
Jotta ohjelma saadaan lähdekoodista ajettavaksi tiedostoksi, täytyy se
kääntää jollain C-kääntäjällä. Käännöksessä on useita vaiheita, joita käsi-
tellään myöhemmin. Ohjelmoijan ei yksinkertaisia ohjelmia tehdessä tarvitse
huomioida eri vaiheita, koska C-kääntäjän liittymäosa osaa useimmiten ajaa
kaikki käännöksen vaiheet automaattisesti.
Hyvin yleistä on kuitenkin, ettei kääntäjä kykene kääntämään lähdekoodia
ajettavaksi ohjelmaksi, koska siinä on virheitä. Tällöin kääntäjä ilmoittaa
virheistä ja kertoo yleensä ainakin virheen tyypin sekä rivin, jolla se
lähdekoodissa esiintyy. Kun näin tapahtuu, kannattaa katsoa lähdekoodin ri-
viä, jolla kääntäjä ilmoittaa virheen olevan. Yleensä virheet, joista
kääntäjä ilmoittaa, ovat välittömästi näkyvissä olevia (esimerkiksi kirjoi-
tusvirheitä). Jos kyseiseltä riviltä ei löydy mitään, mikä vaikuttaa vir-
heelliseltä, kannattaa katsoa paria edellistä riviä.
Monet kääntäjät eivät yhdestä virheestä keskeytä käännöstä, vaan jatkavat
eteenpäin, jolloin virheitä saatetaan luetella useampia. Useimmat kääntäjät
saattavat myös varoittaa jostain asioista. Nämä varoitukset eivät ole varsi-
naisia virheitä, ja ohjelma yleensä kääntyy varoituksista huolimatta. Ne
saattavat kuitenkin tarkoittaa, ettei ohjelma tule toimimaan aivan kunnolla,
eli ohjelman ei pitäisi käännettäessä tuottaa edes varoituksia.
Kun ohjelma lopulta kääntyy kunnolla ilman virheitä tai varoituksia, pitäisi
tuloksena olla ajettava tiedosto, jota voi jo kokeilla. Kuitenkaan se, että
kääntäjä on onnistunut kääntämään ohjelman lähdekoodista ajettavaan muotoon,
ei takaa ohjelman toimivan halutusti. Ohjelmassa voi olla kaikenlaisia suun-
nitteluvirheitä, "bugeja", jotka saattavat olla hyvinkin vaikeasti
löydettävissä. Tällaisten vikojen etsintää eli "debuggausta" käsitellään
kurssissa vasta myöhemmin, kun on aluksi käyty läpi itse C-kielen perusteet.
Kurssin esimerkkiohjelmien pitäisi kuitenkin toimia millä tahansa kunnolli-
sella C-kääntäjällä käännettynä. (Niiden esimerkkiohjelmien kohdalla, joita
tämä ei koske, on erillinen maininta.)
Tässä vaiheessa voimmekin jo kokeilla yksinkertaisen ohjelman kääntämistä.
Käynnistä tekstieditori ja kirjoita siihen seuraava:
#include <stdio.h>
int main(int ac, char **av)
{{
puts("Hello World!");
return 0;
}
Tallenna ohjelma vaikka tiedostoon nimeltä "hello.c". Tämä hello.c on ohjel-
man lähdekoodi, kuten ylempänä selitettiin. Nyt voidaan kokeilla käyttää C-
kääntäjää, jotta saadaan tämä hello.c käännettyä ajettavaan muotoon, vaikka
tiedostoksi nimeltä "hello". Käännös tapahtuu ylläkäsitellyillä kääntäjillä
seuraavasti:
{3Aztec
(Jos joku käyttää Aztecia, voi ehkä osata tehdä järkevämminkin.)
Yleisesti: cc <lähde>.c
ln <lähde>.o -l<kohde>
Eli hello.c käännetään:
cc hello.c
ln hello.o -lhello
{3DICE C
Yleisesti: dcc <lähde> -o <kohde>
Eli hello.c käännetään:
dcc hello.c -o hello
{3GCC
Yleisesti: gcc <lähde> -o <kohde>
Eli hello.c käännetään:
gcc hello.c -o hello
{3SAS/C
Yleisesti: sc <lähde> link to <kohde>
Eli hello.c käännetään:
sc hello.c link to hello
Kaikki kääntäjät osaavat paljon muutakin, joten kannattaa tutustua huolelli-
sesti käyttämäänsä kääntäjään.
Jos käännös on onnistunut, pitäisi tuloksena olla ajettava tiedosto "hello".
Kokeile ajaa se; sen pitäisi tulostaa "Hello World!" CLI/Shell-ikkunaan,
josta se ajettiin.
{3Välissä kehittyneempää tietoa kiinnostuneille
Yksi syy, miksi C-kieltä on virheellisesti pidetty tehottomana, on se, että
tällainenkin esimerkki tuottaa varsin suuren ajettavan tiedoston. Tämän
"hello":n koko käännettynä on kääntäjäkohtaisesti n. 2-5kB. Tästä kuitenkin
itse lähdekoodissa oleva osuus on enintään hieman yli 200 tavua, vaihdellen
kääntäjän ja käytettyjen optioiden mukaan. Kokonaisuuden suhteessa suuri ko-
ko johtuu siitä, että ohjelman lopullisessa ajettavassa muodossa on linkat-
tuna mukaan kaksi alustuskooditasoa. (Linkkauksesta puhutaan myöhemmin kurs-
sissa.) Niistä matalampitasoinen yleensä tallentaa pino-osoittimen arvon
(jotta ohjelmasta voidaan poistua helpommin riippumatta poistumiskohdasta),
avaa dos.library-systeemikirjaston ja varautuu siihen, että ohjelma on käyn-
nistetty Workbenchistä. Tämän jälkeen kutsutaan korkeamman tason alustuskoo-
dia, joka valmistelee C:n stdio-tiedostot ja muotoilee komentorivillä anne-
tut parametrit C:n main():in käyttämään muotoon. Vasta kaiken tämän jälkeen
kutsutaan main():ia eli varsinaista käyttäjän tekemää ohjelmaa. Alustuskoodi
itsessäänkään ei vie niin paljon tilaa, vaan ohjelmaan joudutaan linkkaamaan
myös C:n tavallisia funktioita, hello.c:n tapauksessa ainakin seuraavia: fo-
pen(), fclose(), fwrite(), write(), puts() ja malloc(). (GCC on tässä poik-
keus; osa näistä rutiineista on sijoitettu ixemul.library-kirjastoon, jol-
loin niitä ei tarvitse sisällyttää jokaiseen ohjelmaan erikseen.)
Suuremmissa ohjelmissa koon kasvu linkatun informaation takia ei ole niin
merkittävää, koska niissä itse ohjelman osuus on huomattavasti suurempi.
Pieniä ohjelmia tehdessään monet kuitenkin haluavat minimoida ohjelmiensa
koon jättämällä kaikki C:n alustukset pois. Tämä vaatii jonkinasteista Ami-
gan käyttöjärjestelmän tuntemusta, koska vain joitain harvoja yksinkertaisia
(lähinnä merkkijonon manipulointiin käytettyjä) C:n standardifunktioita voi-
daan kutsua. Tällä tavalla tehtynä esimerkisi hello.c voitaisiin tehdä seu-
raavasti:
#include <exec/types.h>
#include <exec/libraries.h>
#include <dos/dos.h>
#ifdef __GNUC__
#include <inline/exec.h>
#include <inline/dos.h>
int callstart(void)
{{
return start();
}
#else
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <proto/exec.h>
#include <proto/dos.h>
#endif
int start(void)
{{
struct Library *SysBase, *DOSBase;
SysBase = *(struct Library **)4;
if (DOSBase = OpenLibrary("dos.library", 0)) {
Write(Output(), "Hello World!\n", 13);
CloseLibrary(DOSBase);
}
return 0;
}
Tämän pitäisi kääntyä ainakin DICE:lla, SAS/C:llä ja GCC:llä, jos osaa aset-
taa optiot oikein ja omistaa tarpeelliset includet. Tuloksena olevan ajetta-
van ohjelman koon pitäisi olla alle 130 tavua DICE:lla tai SAS/C:llä
käännettynä, GCC:llä tämän tyyppinen tekniikka toimii varsin huonosti. Pal-
joa tätä pienemmäksi ei vastaavaa ohjelmaa saisi assemblerillakaan. Kokoa
voisi hieman vähentää jättämällä pois rekisterien tallentaminen pinoon, joka
poikkeuksellisesti ei ole tässä tarpeellista.
{3C-kielen muotoilu
C-kieli on hyvin vapaamuotoista: siinä voi olla jokaisen elementin välissä
rajoittamaton määrä "tyhjää" (tyhjäksi lasketaan välilyönnit, tabulaattorit,
rivinvaihdot sekä kommentit). Ottakaamme esimerkiksi hello.c:ssä käytetty
funktion puts() kutsu:
puts("Hello World!");
puts ( "Hello World!" ) ;
puts
(
"Hello World!"
)
;
Kaikki ylläolevat tarkoittavat C-kääntäjän näkökulmasta samaa, koska ne
koostuvat samoista viidestä elementistä: "puts", "(", "Hello World!", ")" ja
";".
Kuten useimmissa muissakin kielissä, myös C-kielessä voidaan ohjelman lähde-
koodiin kirjoittaa kommentteja, jotka eivät vaikuta itse ohjelmaan mi-
tenkään, koska kääntäjä ei huomioi niitä. C-kielen kommentit alkavat mer-
keillä "/*" ja loppuvat merkkeihin "*/" ja voivat sijaita lähes missä vain.
Yllä esimerkkinä käytetty tarkoittaisi edelleen samaa, vaikka sen kirjoit-
taisi muotoon:
puts /* this is a comment */ (
"Hello World!"
/*
* this is a block comment
*/
);
Lisäksi monet C-kääntäjät hyväksyvät C++-tyylisiä kommentteja. Nämä alkavat
merkeillä "//" ja jatkuvat rivin loppuun. Edelleen samaa esimerkkiä
käyttäen:
puts ( // This is a C++ comment
"Hello World!");
Aivan erityinen "tyhjän" merkki on "\" rivin lopussa. Tällä ilmaistaan ri-
vin jatkumista seuraavan rivin alusta, ja se voi vaikka jakaa jonkun normaa-
listi yhtenäisen koodin osan muuttamatta sen merkitystä:
pu\
ts("Hello World!");
Rivin lopussa oleva "\" ei kuitenkaan yleensä ole tällaisissa tilanteissa
hyödyllinen. Käytännössä sitä käytetään yleisimmin esikäsittelijän ohjausko-
mentojen yhteydessä. Ne ovat varsinaiseen C-koodiin nähden poikkeuksellisia,
koska ne ovat aina rivin mittaisia. Näistä lisää myöhemmin.
Joillakin kääntäjillä voi myös kommenteilla hajottaa osia muuttamatta niiden
merkitystä. Seuraava toimii useilla kääntäjillä:
pu/* */ts("Hello World!");
Tällainen saattaa myös toimia:
pu/*
*/ts("Hello World!");
Näiden kommenttijakojen toimivuuteen ei kuitenkaan kannata luottaa.
Usein vaaditaan osien välille vähintään yksi tyhjä, jotta ne voidaan erottaa
toisistaan, esimerkiksi hello.c:ssä on eräällä rivillä:
int main(int ac, char **av)
Mahdollisimman paljon tiivistettynä tämä olisi:
int main(int ac,char**av)
Tämän enempää ei voida tiivistää, koska "intmain" tai "intac" näyttäisivät
yhdeltä "sanalta".
Vaikka C-kieltä voidaan muotoilla näin vapaasti, käytetään kuitenkin yleensä
täysin säännönmukaista muotoilua ohjelman rakenteen selkeyden vuoksi. Mitään
virallista tapaa ei varsinaisesti ole, mutta on kuitenkin "yleinen
käytäntö", jota useimmat C-ohjelmoijat seuraavat. Tämä käytäntö ei kuiten-
kaan ole kovinkaan tarkka, vaan aika yleinen, joten jotkut asiat vaihtelevat
aina ohjelmoijan mukaan.
Yleiseen käytäntöön kuuluu, että jokainen ohjelman lohko ("{"- ja "}"-merk-
kien välissä oleva ohjelmaosuus) sisennetään. Kuitenkin sisennyksen määrä ja
aaltosulkeiden sijoittelu vaihtelee. Yleensä sisennys on 2-4 merkkiä. Jotkut
käyttävät 8 merkin sisennystä, mikä tosin vaikeuttaa ohjelman rakenteen hah-
mottamista jo siinä määrin, että ohjelmoijan on vaikea hyödyntää C:n raken-
teellisuuden etuja, ja ohjelman tekninen toiminta kärsii (oma "teoria" - ei
ole pakko uskoa). Tässä pari esimerkkiä sisennyksestä ja aaltosulkeiden si-
joittelusta (typerä esimerkkifunktio, ei kokeiltavaksi tai ymmärrettäväksi
tarkoitettu):
/*
* yksinkertainen sijoittelu, 2 merkin sisennykset
*/
void procedure(void)
{{
int i, s;
for (i = 10, s = 5; i; --i)
{
s += i * 3;
if (i == 5)
{
printf("at i = 5, s = %d\n", s);
i -= s / 10;
}
else
{
printf("loop iteration %d\n", i);
s = -s;
}
}
puts("done");
}
/*
* hieman erikoisempi sijoittelu, 4 merkin sisennykset
*/
void procedure(void)
{{
int i, s;
for (i = 10, s = 5; i; --i)
{
s += i * 3;
if (i == 5)
{
printf("at i = 5, s = %d\n", s);
i -= s / 10;
}
else
{
printf("loop iteration %d\n", i);
s = -s;
}
}
puts("done");
}
/*
* tiiviimpi sijoittelu, 2 merkin sisennykset
* (tässä kurssissa käytetty muoto)
*/
void procedure(void)
{{
int i, s;
for (i = 10, s = 5; i; --i) {
s += i * 3;
if (i == 5) {
printf("at i = 5, s = %d\n", s);
i -= s / 10;
} else {
printf("loop iteration %d\n", i);
s = -s;
}
}
puts("done");
}
Ylläolevissa esimerkeissä tulee esille myös muita muotoiluun liittyviä
asioita, kuten välien käyttö. Kun kutsutaan/määritellään funktioita (tässä
niitä ovat "procedure", "printf" ja "puts"), ei yleensä laiteta väliä sul-
keiden ympärille eikä sisäpuolelle. (Tosin jotkut - aika harvat - käyttävät
väliä rutiinin nimen ja "("-merkin välissä.) Kun taas käytetään C:n raken-
teita (tässä: "for", "if"; muita: "while", "switch"), laitetaan yleensä sul-
keiden ympärille välit (jälleen kerran poikkeuksia löytyy). Sulkeiden
sisäpuolella harvat käyttävät missään yhteydessä välejä.
Pilkun jälkeen tulee aina väli, ennen pilkkua ei. (Tosin tämäkin voi vaih-
della pilkkun käyttöpaikan mukaan.)
Lausekkeissa yleensä käytetään välejä eri operaattoreiden ("=", "==", "+",
"-" jne.) molemmin puolin, poikkeuksena esimerkiksi "-" silloin, kun se tar-
koittaa negatiivista arvoa eikä vähennyslaskua.
Kommentit sijoitetaan yleensä rivin loppuun tai tyhjälle riville. Usein
tyhjälle riville laitettu kommentti kannattaa laittaa useammalle riville
esimerkkien selostuksissa näkyvään tyyliin.
Enemmänkin "käytäntöjä" on, mutta ne käyvät ilmi myöhemmin itse kielen opet-
telun yhteydessä.
Jos joutuu lukemaan paljon toisten kirjoittamaa C-koodia ja on muotoilun
suhteen yhtä neuroottinen kuin minä, kannattaa etsiä käsiinsä "indent"- oh-
jelma, joka automaattisesti muotoilee C-sorsia sille annettujen optioiden
mukaisesti.
{3Funktiot
C-kielisen ohjelman eräitä oleellisia osia ovat funktiot (tai "rutiinit",
englanniksi "functions" tai "procedures"). Funktioita voidaan määritellä ja
kutsua. Esimerkkiohjelma hello.c määritteli funktion main() ja kutsui funk-
tiota puts():
int main(int ac, char **av) /* funktion esittely */
{{ /* funktion koodin määrittely alkaa */
puts("Hello World!"); /* kutsutaan funktiota puts() */
return 0; /* palautetaan 0 */
} /* funktion koodin määrittely loppuu */
Funktion esittely määrittelee funktion nimen, minkätyyppisen arvon funktio
palauttaa sekä minkätyyppiset parametrit sillä on (tyypeistä tarkemmat se-
lostukset myöhemmin). Esimerkissä funktion nimi on main, se palauttaa arvon,
jonka tyyppi on int (eräs kokonaislukutyyppi), parametrien tyypit ovat int
ja char **, joille annetaan muuttujanimikkeet ac ja av funktion sisällön
ajaksi, eli ne ovat käytettävissä kuten mitkä tahansa paikalliset muuttujat
(muuttujistakin lisää tietoa myöhemmin).
Funktion esittelyn muuttujien määrittelylle on myös toinen tapa. Ylläoleva
ja tässä kurssissa yleensä käytetty on ANSI:n mukainen tapa. Jotkut
käyttävät vanhaa K&R tyyliä:
int main(ac, av)
int ac;
char **av;
{{
puts("Hello World!");
return 0;
}
Funktiolla main() on erityinen merkitys, sillä C-ohjelman suoritus kutsuu
sitä automaattisesti. Tämän takia ovat main():in parametrien ja palautuksen
tyypit aina samat, vaikka ohjelma ei käyttäisikään parametreja mihinkään.
Funktion varsinainen sisältö tulee esittelyn jälkeen aaltosulkeiden välissä.
Funktion sisältö määrää, mitä funktio tekee, kun sitä kutsutaan.
Kun funktiosta poistutaan, se voi palauttaa jonkin arvon. Funktiomme main()
palauttaa arvon, joka on tyypiltään int, kuten on määritelty funktion esit-
telyssä. Arvo palautetaan komennolla (yksi C-kielen kahdesta komennosta)
"return", jolla asetetaan palautusarvo ja poistutaan funktiosta. hello.c:n
tapauksessa on palautusarvona luku 0. Mitään return:in jälkeisiä toimintoja
funktiossa ei suoriteta, jos muuttaisimme järjestystä hello.c:ssä:
int main(int ac, char **av)
{{
return 0;
puts("Hello World!"); /* ei koskaan suoriteta */
}
Tässä ei ohjelman suoritus edes pääsisi puts():in kutsuun, koska funktiosta
on poistuttu jo ennen sitä.
Komentoa "return" voidaan käyttää myös ilman palautusarvoa, jos funktio on
määritelty sellaiseksi, joka ei palauta mitään (eli esittelyssä on palautus-
tyypiksi määritelty pseudo-tyyppi "void"). Tyyppiä "void" voidaan käyttää
myös ilmaisemaan, ettei funktiolle anneta parametreja.
/*
* funktio, joka ei ota parametreja eikä palauta mitään
*/
void func1(void)
{{
return;
}
/*
* kun funktio ei palauta mitään, voidaan return-komento jättää pois,
* sillä funktiosta poistutaan automaattisesti myös kun funktio loppuu
*/
void func2(void)
{{
}
Seuraavaksi esimerkkiohjelma, joka demonstroi sellaisen funktion kutsumista,
joka ei ota parametreja:
#include <stdio.h>
void test(void)
{{
puts("testing...");
}
int main(int ac, char **av)
{{
test();
puts("ok");
return 0;
}
Tämän esimerkin pitäisi tulostaa shell-ikkunaan:
testing...
ok
Tässä funktion test() voisi määritellä vanhaan tyyliin ilman "void":ia para-
metrien korvikkeena:
void test()
{{
puts("testing...");
}
Funktioiden määrittelemistä ja kutsumista käsitellään myöhemmin enemmän.
Aluksi on kuitenkin syytä perehtyä hieman tarkemmin tyyppeihin ja muuttu-
jiin.
{3Muuttujat ja tyypit alustavasti
C-kielessä on muuttujille määriteltävissä useita eri tyyppejä. Perustyyppejä
on tehokas käsitellä, koska ne ovat samoja tyyppejä, joita koneen prosessori
osaa käsitellä suoraan.
Ennen tyyppien selitystä ja muuta kurssissa tulevaa on syytä käsitellä joi-
takin tietokoneiden toimintaan liittyviä peruskäsitteitä, jotka lienevät mo-
nille jo ennestään tuttuja:
Bitti (englanniksi "bit"): Nykyisten tietokoneiden toiminta pohjautuu digi-
taalisiin signaaleihin (vastakohtana olisivat esimerkiksi stereolaitteet,
joissa käytetään analogisia signaaleita), joilla on vain kaksi mahdollista
tilaa. Näistä tiloista käytetään numeerisia ilmaisuja 0 ja 1. Käytännössä
nämä signaalit liikkuvat sähkönä johtimia pitkin, ja numeerisia merkintöjä
vastaavat yleensä jännitteet 0V (eli 0) ja +5V (eli 1). Tietoa käsiteltäessä
tällaisesta yksittäisestä signaalista (ja samalla tiedon pienimmästä mahdol-
lisesta yksiköstä) käytetään nimitystä bitti.
Tavu (englanniksi "byte"): Koska bitillä voi ilmaista vain lukuja 0 ja 1,
yhdistetään yleensä useampia bittejä, jotka yhdessä voivat esittää hyödylli-
sempiä lukuja (käytännössä tämä tehdään rinnakkaisilla johtimilla). Yleensä
pienin tällä tavalla käsitelty tiedon yksikkö on tavu, eli 8 rinnakkaista
bittiä. Tavun bitit numeroidaan nollasta seitsemään. Tavun arvo on päällä
(eli tilassa 1) olevien bittien arvojen summa. Bitin arvo on 2^n (jossa ^
tarkoittaa potenssiin korotusta ja n on bitin numero). Bitti numero 7 on
siis tavun eniten merkitsevä, ja bitti numero 0 vähiten merkitsevä bitti.
Kun tavun arvo kirjoitetaan binäärimuotoon (2-kantainen luku), aloitetaan
eniten merkitsevästä bitistä.
Heksadesimaalinotaatio: Koska 10-kantaiset luvut eivät sovellu kovin hyvin
tietokoneissa käytettäviksi (ne eivät mene bitteinä tasan), käytetään moniin
tarkoituksiin 16-järjestelmää (heksadesimaalilukuja), jossa käytetään nume-
roiden 0-9 lisäksi kirjaimia a-f lukujen kirjoittamiseen. Heksadesimaalilu-
vun tunnuksena on C-kielessä 0x (assemblerissa se on $). Heksadesimaalilu-
vuissa on se etu, että jokainen numero vastaa suoraan neljää bittiä, eli ta-
vun arvo on aina ilmaistavissa kahdella heksanumerolla. Tavun arvo on siis
väliltä 0x00 (desimaalina 0) - 0xff (desimaalina 255).
Muisti: Tiedon ilmaisemisesta hetkellisinä signaaleina ei ole sellaisenaan
mitään hyötyä, vaan tietoa täytyy voida säilyttää jotenkin. Tieto säily-
tetään muistin avulla, joka on periaatteessa vain alue peräkkäisiä tavuja.
Yleisimmin muistina käsitetty muisti on RAM-muistia ("Random Access Memo-
ry"), johon voidaan kirjoittaa ja josta voidaan lukea. Amigassa dos-komento
"avail" kertoo vapaana olevan RAM-muistin määrän, miten paljon sitä enim-
millään voisi olla ja miten paljon sitä on käytössä. (Amigassa RAM-muisti on
edelleen jaoteltu kahdentyyppiseen muistiin: chip-muisti, joka on keskusyk-
sikön lisäksi kaikkien erikoisprosessoreiden osoitettavissa ja fast-muisti,
joka on nopeampaa, koska keskusyksikkö saa sillä enemmän väyläaikaa.) Muis-
tin suhteen ei sinänsä ole olemassa käsitteitä "vapaana" tai "käytössä",
vaan Amigan käyttöjärjestelmä (kuten useimmat muutkin käyttöjärjestelmät)
pitää lukua RAM-muistin käytöstä. Muistin "määrä" lasketaan tavuissa.
Lisäksi on ROM-muistia ("Read Only Memory"), jota voi vain lukea. Amigassa
keskeisimmät osat käyttöjärjestelmästä ovat ROM-muistissa. (Juuri tästä on
kyse, kun puhutaan "Kickstart ROM":ista.)
Muistiosoite: Muistia käsiteltäessä on kyettävä ilmaisemaan, mitä kohtaa
muistissa halutaan käsitellä. Tämän takia muisti on kartoitettu osoitteiksi.
Periaatteessa muistiosoite ilmoittaa vain, monesko tavu muistia on kyseessä,
tosin kaikissa osoitteissa ei välttämättä ole muistia. Motorolan 680x0-sar-
jan prosessoreissa (joita Amigassa käytetään) muistiosoitteet ovat 32-bitti-
siä lukuja (tosin 68000/68010 huomioivat vain vähiten merkitsevät 24 bit-
tiä), eli neljän tavun mittaisia. Muistiosoitteet voivat siis periaatteessa
olla väliltä 0 - 0xffffffff (desimaalina 4294967295 - hyvä esimerkki siitä,
miksi heksadesimaaliluvut ovat käytännöllisiä). Amigan RAM-muistista chip-
muisti alkaa yleensä osoitteesta 0 ja jatkuu enintään osoitteeseen 0x1fffff
(jos on 2MB chip-muistia). 16-bittinen fast-muisti (60000/68010-pohjaisissa
koneissa oleva fast-muisti) sijaitsee osoitteesta 0x200000 alkaen enintään
8MB eteenpäin. 32-bittinen fast-muisti (jonka osoittamiseen tarvitaan
vähintään 68020) alkaa osoitteesta 0x7800000. Amigan Kickstart ROM sijaitsee
joko osoitteesta 0xf80000 (2.0 ja uudemmat versiot) tai 0xfc0000 osoittee-
seen 0xffffff. Näiden muistityyppien lisäksi on Amigassa sekä kiinteitä
pseudomuistipaikkoja, joiden avulla ohjataan erikoisprosessoreita (osoitteet
0xdff???) ja CIA-piirejä (osoitteet 0xbfe?01 ja 0xbfd?00), että oheislait-
teiden ohjausosoitteita.
Pino (englanniksi "stack"): Jokaisella käynnissä olevalla ohjelmalla on alue
muistia, jota kutsutaan pinoksi. Amigassa käynnistettävien ohjelmien pinon
kokoa voi muuttaa shellistä stack-komennolla ja Workbenchistä ohjelman iko-
nin tiedoilla. Pinoa käytetään esimerkiksi ohjelman kutsuessa aliohjelmia
(C:ssä funktiokutsut) paluuosoitteen säilyttämiseen. Pino toimii pino-osoit-
timella, joka asetetaan aluksi osoittamaan muistialueen loppuun. Aina kun
pinoon tallennetaan jotain, vähennetään ensin pino-osoittimesta tallennetta-
van olion koko, ja sitten asetetaan pino-osoittimen osoittamaan kohtaan
olion sisältö. Pino-osoitin osoittaa siis aina viimeksi pinoon tallennetun
olion alkuun. Kun pinosta otetaan jotain pois, otetaan pino-osoittimen
osoittamasta kohdasta olion arvo ja lisätään pino-osoittimeen sen koko. Pi-
nosta on siis aina otettava pois kaikki, mitä sinne laitetaan päinvastaises-
sa järjestyksessä kuin missä ne on laitettu sinne. C-kielellä ohjelmoitaessa
ei tarvitse sen enempää välittää pinon toiminnasta, mutta koska Amigalla on
ohjelmilla tavallisesti kiinteät pinot, kannattaa välttää käyttämästä run-
saasti pino-muistia vaativaa ohjelmointitekniikkaa.
C-kielen yksinkertaisimpia tyyppejä ovat kokonaisluvut. Näitäkin on usean
tyyppisiä (erikokoisia, etumerkillisiä ja etumerkittömiä). Tässä ovat tyyp-
pien koot lueteltuna sellaisina kuin ne ovat yleisimmin Amigalla. Jos ei
tiedä, minkä kokoisina kääntäjässä on toteutettu nämä, kannattaa katsoa inc-
lude-tiedostosta "limits.h" (include-tiedostoista lisää myöhemmin).
char
signed char
"char" on tavun mittainen eli 8-bittinen kokonaisluku. Arvo voi olla -128 -
127. Tätä tyyppiä käytetään usein tekstin yhteydessä, koska ASCII-tekstissä
jokainen merkki on yksi tavu.
Tämä tyyppi on etumerkillinen, eli jos eniten merkitsevä bitti on päällä,
ilmaisee se luvun olevan negatiivinen. Negatiivinen arvo lasketaan vastaava-
na positiivisena arvona vähennettynä nollasta, eli suurin negatiivinen luku
(-1) on 0xff, pienin negatiivinen luku (-128) on 0x80.
Joissain kääntäjissä on optio, jota käytettäessä "char" tarkoittaa "unsigned
char":ia, ja etumerkillistä tavua varten täytyy käyttää "signed char":ia.
unsigned char
Kuten char, mutta etumerkitön. Arvo voi olla 0 - 255 (0x00 - 0xff).
short
signed short
short int
signed short int
"short" on etumerkillinen, 16-bittinen (kahden tavun mittainen) kokonaislu-
ku. 680x0-prosessoreilla short tallennetaan muistiin kahteen peräkkäiseen
tavuun, eniten merkitsevä tavu ensin (pienemmässä muistiosoitteessa).
68000/68010:llä on shortin oltava parillisessa osoitteessa (shortin osoite
on sen ensimmäisen tavun osoite). Arvo voi olla -32768 - 32767.
unsigned short
unsigned short int
Kuten short, mutta etumerkitön. Arvo voi olla 0 - 65535 (0x0000 - 0xffff).
int
signed int
"int" on yleensä samanlainen kuin "long". Joissain kääntäjissä se voi olla
joko optiolla tai aina (Aztec) kuten "short".
unsigned int
Kuten int, mutta etumerkitön.
long
signed long
long int
signed long int
"Long" on etumerkillinen, 32-bittinen (neljän tavun mittainen) kokonaisluku.
680x0-prosessoreilla long tallennetaan muistiin neljään peräkkäiseen tavuun,
eniten merkitsevä tavu ensin. 68000/68010:llä on longin oltava parillisessa
osoitteessa. Arvo voi olla -2147483648 - 2147383647.
unsigned long
unsigned long int
Kuten long, mutta etumerkitön. Arvo voi olla 0 - 4294967295 (0x00000000 -
0xffffffff).
long long
signed long long
long long int
signed long long int
Joissain kääntäjissä (ainakin GCC:ssä) on käytettävissä 64-bittinen koko-
naislukutyyppi. 680x0-prosessorit eivät suoraan tue 64-bittisiä lukuja, jo-
ten näiden käsittely on hieman monimutkaisempaa ja hitaampaa kuin muiden ko-
konaislukutyyppien. Etumerkillisen, 64-bittisen kokonaisluvun arvo voi olla
-9223372036854775808 - 9223372036854775807.
unsigned long long
unsigned long long int
Kuten long long, mutta etumerkitön. Arvo voi olla 0 - 18446744073709551615
(0x0000000000000000 - 0xffffffffffffffff).
{3Muuttujan määrittely
Muuttujaa määriteltäessä on muuttujalle annettava jokin tyyppi ja nimi.
Tyyppi voi olla esimerkiksi jokin ylläolevista kokonaislukutyypeistä. Muut-
tujan nimi voi olla periaatteessa minkä pituinen vain (tosin jotkut vanha-
naikaiset kääntäjät tunnistavat muuttujan vain nimen ensimmäisen kahdeksan
merkin perusteella - ANSIn mukaan tarvitsee huomioida vähintään ensimmäiset
31 merkkiä) ja voi sisältää kirjaimia a-z (sekä isoja että pieniä), numeroi-
ta sekä "_"-merkkejä. Muuttujan nimen ensimmäinen merkki ei saa olla numero.
Isot kirjaimet ja pienet kirjaimet eivät vastaa toisiaan, joten voi olla
esimerkiksi muuttuja nimeltä A ja muuttuja nimeltä a ilman, että ne sekoit-
tuvat keskenään.
Muuttujan nimeksi ei voi antaa mitään C-kielen kannalta jotain merkitseviä
avainsanoja (kuten tyyppien nimiä).
Esimerkkejä muuttujien määrittelystä:
/*
* määritellään muuttuja alpha tyyppiä long:
*/
long alpha;
/*
* muuttujia voidaan määritellä myös useampia kerralla:
*
* määritellään muuttujat a, b ja c, tyyppiä short:
*/
short a, b, c;
/*
* joka on sama asia kuin:
*/
short a;
short b;
short c;
/*
* määritellään muuttuja mychr tyyppiä unsigned char:
*/
unsigned char mychr;
Usein muuttujia nimetessä kannattaa käyttää nimitystä, joka kuvaa (yleensä
lyhennettynä englanninkielestä) jotenkin muuttujan tehtävää. Jos muuttujaa
käytetään vain hetkellisesti, eikä sille löydy mitään loogista lyhennettä,
nimetään muuttuja usein yhdellä tai kahdella kirjaimella. Muuttujien ni-
meäminen esim. suomeksi tai sanoin, jotka eivät liity mitenkään asiaan, on
kaikkea muuta kuin suositeltavaa.
Muuttujaa voidaan määrittelyn jälkeen käyttää ohjelman siinä lohkossa (eli
aaltosulkeiden välissä olevassa osassa), jossa muuttuja on määritelty. Jos
muuttujan määrittely ei ole minkään funktion sisällä, on muuttuja
käytettävissä kaikissa funktioissa koko loppuohjelmassa, eli se on "globaa-
li" muuttuja. Muulloin on kyseessä paikallinen muuttuja.
Funktion parametreissa nimetty muuttuja on käytettävissä aina kyseisen funk-
tion sisällä.
Muuttuja vaatii tietysti paikan, jossa sitä voi säilyttää. Säilytystilaa se
tarvitsee tyyppinsä koon verran. Periaatteessa C-kääntäjä saa säilyttää
muuttujia miten haluaa, mutta ainakin Amigalla tuntuvat kaikki kääntäjät te-
kevän sen suunnilleen samoin. Globaalit muuttujat sijoitetaan ohjelman da-
ta/bss-hunkkeihin, jotka ovat ohjelman ajon ajan kiinteästi muistissa olevia
alueita. Paikallisista muuttujista osa sijoitetaan keskusyksikön rekisterei-
hin (yleensä eniten käytetyt) ja osalle varataan tilaa pinosta kyseisen
funktion suorituksen ajaksi.
{3Lauseet ja lausekkeet
C-kielinen ohjelma koostuu useimmiten pääasiassa lauseista (englanniksi
"statements"), jotka ovat funktion sisällön toiminnallisia osia. Tyhjä lause
olisi pelkkä ";"-merkki. Lauseet ovat usein joitakin rakenteita (if, while,
for, switch jne.) tai komentoja (return, goto). Lause voi myös olla lauseke
(englanniksi "expression"), jonka perässä on ";"-merkki. Myös komentolausei-
den perässä on ";"-merkki. Lauseke koostuu puolestaan yleensä yhdestä tai
useammasta lausekkeesta, joiden välissä tai yhteydessä on operaatioita. Pe-
ruslausekkeita (eivät koostu useammasta lausekkeesta operaattoreilla yhdis-
tettynä) ovat esimerkiksi muuttujat, vakiot ja funktiokutsut. Lausekkeella
on lähes poikkeuksetta arvo, mutta lauseke ei välttämättä tee mitään. Yksin-
kertainen esimerkki lausekkeen muodostamasta lauseesta, joka ei tee mitään:
0;
Tässä on kyseessä perustyyppiä oleva lauseke, vakio. Lausekkeen arvo on va-
kion arvo, eli 0.
Hieman hyödyllisempi esimerkki lausekkeesta voisi olla arvon antaminen muut-
tujalle. Tämä tehdään "="-operaattorilla. Esimerkiksi jos meillä on muuttuja
"a", joka on jotain kokonaislukutyyppiä, voimme antaa sille arvon 0 seuraa-
valla tavalla:
a = 0;
Operaattori "=" toimii siten, että sen molemmilla puolilla on operandeina
lausekkeet. Myös tämä kokonaisuus on lauseke, joten jos meillä on muuttujat
a ja b (jotka ovat jossain määrin yhteensopivan tyyppisiä):
b = a = 0;
Tämä antaa siis ensin muuttujalle a arvon nolla, sitten antaa muuttujalle b
lausekkeen (a = 0) arvon, joka on myös 0. (Peräkkäiset "="-operaatiot suori-
tetaan aina oikealta vasemmalle.) Tämä on siis sama kuin:
a = 0;
b = a;
Operaattorin "=" vasemmalla puolella olevalla operandilla on sellainen vaa-
timus, että sen on oltava muutettavissa. (Englanniksi tällaisen vaatimuksen
täyttävää lauseketta kutsutaan nimellä "modifiable lvalue".) Toistaiseksi
kurssissa on käsitelty vasta yksi mahdollinen lauseketyyppi, joka täyttää
tämän vaatimuksen, eli muuttuja.
Lausekkeita voivat olla myös yksinkertaiset matemaattiset laskutoimitukset;
normaali laskujärjestys pätee ja sulkeita voidaan käyttää sen muuttamiseen:
int a, b, c;
a = 1 + 5 * 8; /* a:n arvoksi tulee 41 */
b = 10 / 4 - a; /* b:n arvoksi tulee -39 */
c = 2 * (a + b); /* c:n arvoksi tulee 4 */
Muita laskennallisia operaattoreita ovat:
% jakojäännös
Käyttö esimerkiksi:
a = 5 % 3; /* a:n arvoksi tulee 2 */
& "ja"-binäärioperaattori
Tuloksessa ovat tilassa 1 vain ne bitit, jotka olivat molemmissa operandeis-
sa päällä, binäärilukuina esimerkiksi:
0111001011010110 (heksana 0x72d6)
& 1010100110101101 (heksana 0xa9ad)
antaa tulokseksi:
0010000010000100 (heksana 0x2084)
Käyttö esimerkiksi:
a = 0xf0f0 & 0x5555; /* a:n arvoksi tulee 0x5050 */
/*
* sama desimaaliluvuilla (vaikeampi hahmottaa):
*/
a = 61680 & 21845; /* a:n arvoksi tulee 20560 */
| "tai"-binäärioperaattori
Tuloksessa ovat kaikki ne bitit ykkösiä, jotka olivat jommassakummassa ope-
randissa päällä, binäärinä esim.:
01001101 (heksana 0x4d)
| 11010110 (heksana 0xd6)
antaa tulokseksi:
11011111 (heksana 0xdf)
Käyttö esimerkiksi:
a = 0x1111 | 0x3232; /* a:n arvoksi tulee 0x3333 */
/*
* sama desimaaliluvuilla:
*/
a = 4369 | 12850; /* a:n arvoksi tulee 13107 */
^ "ehdoton tai"-binäärioperaattori
Tuloksessa ovat kaikki ne bitit ykkösiä, jotka olivat jommassakummassa ope-
randissa mutta eivät molemmissa päällä, binäärinä esim.:
10110100 (heksana 0xb4)
^ 11011001 (heksana 0xd9)
antaa tulokseksi:
01101101 (heksana 0x6d)
Käyttö esimerkiksi:
a = 0x1f42 ^ 0x3729; /* a:n arvoksi tulee 0x286b */
/*
* sama desimaaliluvuilla:
*/
a = 8002 ^ 14121; /* a:n arvoksi tulee 10347 */
~ "ei"-binäärioperaattori
Tämä on edeltämäänsä lausekkeeseen liittyvä operaattori (englanniksi "unary
operator").
Tuloksen bitit ovat päinvastaisessa tilassa kuin operandin bitit, binäärinä
esim.:
~ 01100101 (heksana 0x65)
antaa tulokseksi:
10011010 (heksana 0x9a)
Käyttö esimerkiksi:
a = ~0x4e82; /* a:n arvoksi tulee 0xb17d */
/*
* sama desimaaliluvuilla:
*/
a = ~20098; /* a:n arvoksi tulee 45437 */
<< bittien siirto vasemmalle
Antaa tulokseksi vasemman operandin bitit siirrettynä vasemmalle oikean ope-
randin verran. Ellei laskussa tapahdu ylivuotoa, niin a << b on sama kuin a
kerrottaisiin 2^b:llä (jossa ^ esittää potenssiin korotusta).
a = 1 << 2; /* a:n arvoksi tulee 4 */
>> bittien siirto oikealle
Antaa tulokseksi vasemman operandin bitit siirrettynä oikealle oikean ope-
randin verran. Jos oikea operandi ei ole negatiivinen, niin a >> b on sama
kuin a jaettaisiin 2^b:llä (jossa ^ esittää potenssiin korotusta).
a = 12 >> 1; /* a:n arvoksi tulee 6 */
{3Vertailuoperaattorit, if-rakenne
Lausekkeissa käytetään myös sellaisia operaatioita, joilla on vain kaksi
mahdollista arvoa: tosi tai epätosi. Tosi tarkoittaa mitä tahansa arvoa, jo-
ka ei ole nolla. Nolla on ainoa epätosi. Näitä operaatioita on kahdentyyppi-
siä: vertailulliset ja loogiset operaatiot. Vertailuoperaattorit vertaavat
molemmin puolin olevien lausekkeiden arvoja. Niitä ovat seuraavat:
== samanarvoinen
!= eriarvoinen
< pienempi
> suurempi
<= pienempi tai sama
>= suurempi tai sama
Näitä voidaan käyttää tähän asti esitettyyn tyyliin, eli esimerkiksi:
a = b == 10;
(Asettaa muuttujan a 1:ksi jos muuttujan b arvo on 10, muussa tapauksessa
nollaa a:n).
Tai jopa:
a >= b;
(Ei tee mitään, koska tulosta ei käytetä mihinkään.)
Tämä ei kuitenkaan ole kovin hyödyllistä. Vertailuoperaatioita käytetään
yleensä silmukoiden (myöhemmin) ja if-rakenteiden yhteydessä:
if (<lauseke>) {
/* ohjelman osa, joka suoritetaan vain jos <lauseke> on tosi */
}
/* ohjelma jatkuu */
tai:
if (<lauseke>) {
/* ohjelman osa, joka suoritetaan vain jos <lauseke> on tosi */
} else {
/* ohjelman osa, joka suoritetaan jos <lauseke> on epätosi */
}
/* ohjelma jatkuu */
Jos "if ()" - tai "else"-kohdan jälkeen tuleva ohjelman osa muodostuu vain
yhdestä lauseesta, voidaan aaltosulkeet jättää pois.
Esimerkkejä if-rakenteesta:
if (a == 5) {
puts("a == 5 on tosi");
a = 10;
}
Jos muuttujan a arvo on 5, tulostaa tekstin "a == 5 on tosi" ja muuttaa a:n
arvon 10:ksi. Huomaa, että lausekkeen perässä ei ole ";"-merkkiä, koska se
ei tässä tapauksessa muodosta lausetta. If-rakenne sen sijaan on kokonaisuu-
tena lause, johon kuuluu perässä oleva toinen lause (tässä tapauksessa
useamman lauseen muodostama ohjelmalohko).
if (a < b)
puts("a on pienempi kuin b");
else if (a == b) {
puts("a on samanarvoinen kuin b");
b = 0;
}
Tässä tapuksessa on ensimmäisen if:n ja else:n jälkeiset aaltosulkeet voitu
jättää pois, toisen if:n jälkeen ei. Tämän voisi kirjoittaa myös jättämättä
aaltosulkeita pois:
if (a < b) {
puts("a on pienempi kuin b");
} else {
if (a == b) {
puts("a on samanarvoinen kuin b");
b = 0;
}
}
Jotkut käyttävät aina aaltosulkeita, vaikka niitä ei tarvittaisikaan.
Tässäkin tapauksessa on havaittavissa, että se voi selkeyttää hieman koodin
rakennetta.
C-kielen grammatiikan ainoa epäselvyys on useamman vailla aaltosulkeita ole-
van if:n jälkeen tulevan else:n tapaus. (Jos joku tietää, miten välttää par-
serissa tämän takia ilmenevän shift/reduce-conflictin keksimättä omia
ylimääräisiä sääntöjään ja monimutkaistamatta parseria pahemmin, niin kerto-
koon). Jos meillä on esimerkiksi:
if (b != 10)
if (a)
puts("text1");
else
puts("text2");
Tämä näyttää selkeältä (erityisesti muotoilun ansiosta), mutta sisältää kui-
tenkin epäselvyyden. Periaatteessa else voisi olla kummalle tahansa if-eh-
dolle vaihtoehto. Tämä kuitenkin tulkitaan siten (kuten esimerkin muotoilus-
sa on otettu huomioon), että else on vaihtoehtona aina sisimmälle mahdolli-
selle if-ehdolle. Hyvä tapa on kuitenkin näissä epäselvissä tapauksissa
käyttää aaltosulkeita:
if (b != 10) {
if (a)
puts("text1");
else
puts("text2");
}
Tästä käy myös ilmi, että if:n ehdon ei tarvitse sisältää vertailuoperaatto-
reita. Tässä sisempi if testaa, onko muuttujan a arvo tosi vai ei, eli
"text2" tulostuu, jos a on 0, muuten tulostetaan "text1".
{3Ensimmäinen osa päättyy...
Tähän asti en ole voinut esittää useampia kokonaisia esimerkkiohjelmia, kos-
ka annettu tieto itse kielestä on vielä hyvin vähäistä. Myöhemmissä osissa
näitä tulee olemaan enemmän. Tarkoitus on käsitellä ainakin seuraavia aihei-
ta jatkossa:
- loogiset operaatiot (&&, ||, !)
- operaattoreiden lyhenteitä
- silmukat (for, while, do { } while)
- switch
- esikäsittelyn ohjaaminen
- lisää tietoa tyypeistä, muuttujista ja funktioista
- main()-funktion parametrit
- merkkijono- ja merkkivakiot
- lisää operaattoreita (*, &, ->, ., ?:, ",")
- operaatioiden suoritusjärjestys
- liukuluvut, liukulukuvakiot
- goto-komento, miksi ei pidä käyttää
- standardeja (ANSI) C-funktioita ja include-tiedostoja, niiden käyttö
- tiedon käsittelytekniikkaa (muisti, tiedostot, muuttujat)
- käännöksen vaiheet
- suuremman projektin kasassapito
- C-kääntäjän toimintaa: C-kielen teoriaa
- bugien etsintä
- C-laajennuksia
- ohjelmointitekniikka: vihjeitä
- Amigan käyttöjärjestelmä: alustavaa tietoa, mistä jatkaa